package com.ztrue.framework.context;

import com.ztrue.framework.core.command.*;
import com.ztrue.framework.core.domain.IAggregateRoot;
import com.ztrue.framework.core.event.DomainEventStream;
import com.ztrue.framework.core.event.IDomainEvent;
import com.ztrue.framework.core.event.IEventService;
import com.ztrue.framework.disruptor.MessageHandler;
import com.ztrue.framework.exception.NotFoundException;
import com.ztrue.framework.handler.IHandlerDefinitionRegistry;
import com.ztrue.framework.handler.IHandlerRegistry;
import com.ztrue.common.log.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 默认的命令消息处理器
 *
 * @author zhangw
 * @date 2016/11/7 15:18
 * @version: 1.0
 */
public class DefaultCommandMessageHander extends MessageHandler<CommandMessage> {
    private static final Logger logger = Logger.getLogger(DefaultCommandMessageHander.class);
    private final IHandlerRegistry handlerRegistry;
    private final IHandlerDefinitionRegistry handlerDefinitionRegistry;
    private final IEventService eventService;

    public DefaultCommandMessageHander(IHandlerRegistry handlerRegistry, IHandlerDefinitionRegistry handlerDefinitionRegistry, IEventService eventService) {
        this.handlerRegistry = handlerRegistry;
        this.handlerDefinitionRegistry = handlerDefinitionRegistry;
        this.eventService = eventService;
    }

    /**
     * 触发事件，将命令发送至对应的命令处理器
     *
     * @param commandMessage 命令消息
     */
    @Override
    public void onEvent(CommandMessage commandMessage) {
        String[] handlerNames = this.handlerDefinitionRegistry.getHandlerNameByTargetType(commandMessage.getCommandClass());
        CommandExecuteContext commandExecuteContext = new CommandExecuteContext(ApplicationContextProvider.APPLICATIONCONTEXT.getRepository());
        for (String handlerName : handlerNames) {
            doHandle(handlerName, commandExecuteContext, commandMessage.getMessage());
        }
    }

    /**
     * 触发事件，将命令发送至对应的命令处理器
     *
     * @param commandContext 命令执行上下文
     * @param command        命令对象
     */
    private void doHandle(String handlerName, ICommandExecuteContext commandContext, ICommand command) {
        try {
            ICommandHandler commandHandler = handlerRegistry.getHandler(handlerName, ICommandHandler.class);
            if (commandHandler == null) throw new NotFoundException("未找到名为[" + handlerName + "]的命令处理器");
            commandHandler.handle(commandContext, command);
            commitAggregateChanges(command, commandContext);
        } catch (Exception ex) {
            logger.error("派发命令时发生异常：" + ex.getMessage(), ex);
        }
    }

    /**
     * 提交聚合根变更
     *
     * @param command
     * @param commandContext
     */
    private void commitAggregateChanges(ICommand command, ICommandExecuteContext commandContext) {
        Collection<IAggregateRoot> trackedAggregateRoots = commandContext.getTrackedAggregateRoots();
        int trackedAggregateRootSize = trackedAggregateRoots.size();
        if (trackedAggregateRootSize == 0) {
            logger.info(String.format("没有命令创建或修改聚合根，commandType[%s]，commandId[%s]", command.getTypeName(), command.getId()));
            return;
        } else if (trackedAggregateRootSize > 1) {
            StringBuffer aggregateRootNameStr = new StringBuffer();
            for (IAggregateRoot aggregateRoot : trackedAggregateRoots) {
                if (aggregateRootNameStr.length() > 0) aggregateRootNameStr.append("|");
                aggregateRootNameStr.append(aggregateRoot.getClass().getName());
            }
            logger.error(String.format("严重逻辑异常，单个命令修改了多个聚合根，commandType[%s]，commandId[%s]，异常聚合根[%s]", command.getTypeName(), command.getId(), aggregateRootNameStr.toString()));
        }
        List<IDomainEvent> changedEvents = new ArrayList<>();
        for (IAggregateRoot aggregateRoot : trackedAggregateRoots) {
            changedEvents.addAll(aggregateRoot.getChanges());
        }
        IAggregateRoot aggreagateRoot = trackedAggregateRoots.iterator().next();
        DomainEventStream eventStream = new DomainEventStream(command.getId(), aggreagateRoot.getUniqueId(), aggreagateRoot.getClass().getName(), aggreagateRoot.getVersion() + 1, changedEvents);
        eventService.commitEvent(new EventCommittingContext(aggreagateRoot, eventStream));
    }
}